package vistop.datatypes;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.eclipse.ui.commands.NotDefinedException;

/**
 * Class representing the single polygon (with its holes) in two
 * different representations. It generates it from unsorted list of its
 * edges where polygons holes have opposite orientation as the polygon itself.
 * 
 * @author Barbara Sepic
 *
 */
public class Polygon {
	private List<List<Vertex>> polygonComponents;
	
	/**
	 * Constructor. It immediately creates the polygon components.
	 * @param edgeList	unordered list of Directed Edges where the orientation 
	 * of the outer most polygon differs from the orientation of its holes.
	 */
	public Polygon(List<Directed<Edge>> edgeList) {
		polygonComponents = new ArrayList<List<Vertex>>();
		findComponents(edgeList);
	}
	
	public Polygon(int i, List<List<Vertex>> components) {
		this.polygonComponents = components;
	}
	/**
	 * Getter.
	 * 
	 * @return The polygon components from originally given edgeList assuming
	 * the orientation of the outer most polygon is different than orientation
	 * of each hole inside the polygon. Each component is represented as a
	 * List of its vertices. First component is outer most polygon.
	 */
	public List<List<Vertex>> getPolygonComponentsVertex() {
		return this.polygonComponents;
	}

	/**
	 * Generates HashMap with starting points of Edges as key and
	 * Edges as values. Note, that it is assumed that outermost 
	 * polygon has opposite orientation than its holes.
	 * 
	 * @param edgeList	list of Edges that form the polygon
	 * @return	HashMap with starting points of Edges as key and
	 * Edges as values
	 */
	private HashMap<Vertex, Directed<Edge>> fillHashMap(
			List<Directed<Edge>> edgeList) {
		HashMap<Vertex, Directed<Edge>> res = 
				new HashMap<Vertex, Directed<Edge>>();
		for (Directed<Edge> e : edgeList) {
			try {
				res.put(e.getStart(), e);
			} catch (NotDefinedException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
		}
		return res;
	}
	
	/**
	 * Generates components of polygon from given edgeList and stores them in 
	 * polygonComponentsEdge attribute. The outermost polygon is the first 
	 * component of polygonComponentsEdge. 
	 * 
	 * @param edgeList	list of Edges of whole polygon including its holes 
	 * (however if it has holes, they should have opposite orientation than 
	 * polygon)
	 * @throws IllegalArgumentException	if edges of each component are not
	 * connected
	 */
	private void findComponents(List<Directed<Edge>> edgeList) 
			throws IllegalArgumentException {
		HashMap<Vertex, Directed<Edge>> hash = fillHashMap(edgeList);
	
		List<Vertex> component = new ArrayList<Vertex>();
		// take one edge from hashmap to start new component
		Directed<Edge> currDirEdge = (Directed<Edge>)hash.values().toArray()[0];
		Edge currEdge = currDirEdge.getGeometricalObject();
		Vertex currEnd = null;
		if (!currEdge.isComplete()) 
			throw new IllegalArgumentException("Edge with id " + 
					currEdge.getId() + " is incomplete");
		try {
			component.add(currDirEdge.getStart());
			hash.remove(currDirEdge.getStart());
			
			currEnd = currDirEdge.getEnd();
		} catch (NotDefinedException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		// to determine which polygon is the outermost polygon
		// we need to keep track of minXvertex - the left most Vertex
		Vertex minVertex = currDirEdge.getGeometricalObject().getMinXVertex();
		// index of component containing minVertex
		int minVertexComponent = 0;
		
		while (!hash.isEmpty()) {
			if (hash.containsKey(currEnd)) {
				currDirEdge = hash.get(currEnd);
				try {
					component.add(currDirEdge.getStart());
					hash.remove(currEnd);
					currEnd = currDirEdge.getEnd();
				} catch (NotDefinedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
			}
			// component finished, start new one
			else {
				// sanity check if component actually closes or not
				Vertex start = component.get(0);
				Vertex end = currEnd; //component.get(component.size()-1).getEnd();
				if (! start.equals(end)) {
					throw new IllegalArgumentException("There is probably at" +
							" least one Edge missing since component is not " +
							"connected.");
					
				} else {  
					// add finished component to result list
					polygonComponents.add(component);
					//start new component
					component = new ArrayList<Vertex>();
					
					// choose new Edge from the hashMap
					currDirEdge = (Directed<Edge>)hash.values().toArray()[0];
					try {
						component.add(currDirEdge.getStart());
						hash.remove(currDirEdge.getStart());
						currEnd = currDirEdge.getEnd();
					} catch (Exception e) {
						// TODO: handle exception
					}
				}
			}
			// this is currently leftmost vertex
			if (currDirEdge.getGeometricalObject().getMinXVertex()
					.compareTo(minVertex) < 0) {
				minVertex = currDirEdge.getGeometricalObject().getMinXVertex();
				minVertexComponent = polygonComponents.size();
			}
		}
		//polygonComponentsEdge2.add(component);
		polygonComponents.add(component);
		
		// if the first component of final list is not the outermost
		// polygon shift it to the beginning
		if (minVertexComponent != 0) {
			List<Vertex> tmp = polygonComponents.get(0);
			polygonComponents.set(0, 
					polygonComponents.get(minVertexComponent));
			polygonComponents.set(minVertexComponent, tmp);
		}
	}
	
	public void printComponents(List<List<Vertex>> l) {
		int j = 0;
		for(List<Vertex> l1 : l) {
			try {
			System.out.println(j++ + ". component:");
			printListV(l1);
			} catch (Exception e) {
				// TODO: handle exception
				System.out.println(e.toString());
			}
		}
	}
	
	private void printListV(List<Vertex> list) {
		int j = 0;
		for (Vertex v : list) {
			System.out.println(j++ + ": " + v.toString());
		}
	}
	
	public static List<List<Vertex>> getTestPolygon() {
		List<List<Vertex>> polygon = new ArrayList<List<Vertex>>();
		List<Vertex> c1 = new ArrayList<Vertex>();
		c1.add(new Vertex(0, 0.0, 0.0, 0.0));
		c1.add(new Vertex(3, 3.0, 0.0, 0.0));
		c1.add(new Vertex(2, 3.0, 3.0, 0.0));
		c1.add(new Vertex(1, 0.0, 3.0, 0.0));
		polygon.add(c1);
		
		List<Vertex> c2 = new ArrayList<Vertex>();
		c2.add(new Vertex(4, 1.0, 1.0, 0.0));
		c2.add(new Vertex(5, 2.0, 1.0, 0.0));
		c2.add(new Vertex(6, 1.5, 2.0, 0.0));
		polygon.add(c2);
		return polygon;
	}
	
	public static List<List<Vertex>> getTestPolygon2() {
		List<List<Vertex>> p = new ArrayList<List<Vertex>>();
		List<Vertex> c1 = new ArrayList<Vertex>();
		c1.add(new Vertex(0, 0.0, 0.0, 0.0));
		c1.add(new Vertex(3, 0.0, 3.0, 0.0));
		c1.add(new Vertex(2, 0.0, 3.0, 3.0));
		c1.add(new Vertex(1, 0.0, 0.0, 3.0));
		p.add(c1);
		
		List<Vertex> c2 = new ArrayList<Vertex>();
		c2.add(new Vertex(4, 0.0, 1.0, 1.0));
		c2.add(new Vertex(5, 0.0, 2.0, 1.0));
		c2.add(new Vertex(6, 0.0, 1.5, 2.0));
		p.add(c2);
		
		return p;
	}
	
	public static List<List<Vertex>> getTestPolygon3() {
		List<List<Vertex>> polygon = new ArrayList<List<Vertex>>();
		List<Vertex> c1 = new ArrayList<Vertex>();
		c1.add(new Vertex(0, 2.0, 1.0, 0.0));
		c1.add(new Vertex(3, 5.0, 1.0, 0.0));
		c1.add(new Vertex(2, 5.0, 4.0, 0.0));
		c1.add(new Vertex(1, 2.0, 4.0, 0.0));
		polygon.add(c1);
		
		List<Vertex> c2 = new ArrayList<Vertex>();
		c2.add(new Vertex(4, 2.0, 2.0, 0.0));
		c2.add(new Vertex(5, 4.0, 2.0, 0.0));
		c2.add(new Vertex(6, 2.5, 3.0, 0.0));
		polygon.add(c2);
		return polygon;
	}
	
}


